#!/usr/bin/env python3
import os
import re
import sys

def clsname2path(cls:str):
    #com.chartboost.sdk.Chartboost
    ret = cls.replace('.', '/')
    ret += '.smali'
    return ret

def find_file_content_match(root, pattern):
    rets=[]
    for root, dirs, files in os.walk(root):
        for f in files:
            if not f.endswith('smali'):
                continue
            file_path = os.path.join(root, f)
            with open(file_path, 'r') as fd:
                try:
                    content = fd.read()
                    if content:
                        if re.search(pattern, content):
                            rets.append(file_path)
                except:
                    pass
    return rets

def find_file_name_match(root, pattern):
    rets=[]
    for root, dirs, files in os.walk(root):
        for f in files:
            file_path = os.path.join(root, f)
            if file_path.endswith(pattern):
                rets.append(file_path)
    return rets


def readfromfile(filepath):
    with open(filepath,'r') as f:
        return f.read()


def javatype2smalitype(t:str):
    ret = ''
    if t == 'boolean':
        ret = 'Z'
    elif t == 'byte':
        ret = 'B'
    elif t == 'short':
        ret = 'S'
    elif t == 'char':
        ret = 'C'
    elif t == 'int':
        ret = 'I'
    elif t == 'long':
        ret = 'J'
    elif t == 'float':
        ret = 'F'
    elif t == 'double':
        ret = 'D'
    elif t.startswith('['):
        ret = '[' + javatype2smalitype(t[1:])
    else:
        ret = 'L' + t.replace('.', '/') + ';'
    return ret


def parmtosig(parm):
    ret=''
    for p in parm:
        ret += javatype2smalitype(p)
    ret=ret.replace('$', '\$')
    return ret



class smaliHook:
    def __init__(self, targetappdir:str) -> None:
        super().__init__()
        self.targetappdir = targetappdir


    def findfile(self, cls):
        if cls[0] == '.':
            return find_file_content_match(self.targetappdir, cls)
        else:
            clspath = clsname2path(cls)
            return find_file_name_match(self.targetappdir, clspath)


    def findMethod(self, content, method, parm):
        methodpattern = '\.method.*%s\(%s\)'%(method, parmtosig(parm))
        print(methodpattern)
        poss=[]
        for pos in re.finditer(methodpattern, content):
            poss.append(pos)
        return poss


    def insertTarget(self, fpath, content:str, pos, target):
        if content.find(target, pos.end()) != -1:
            print("already hooked")
            return
        localstart = content.find(".locals", pos.end())
        nextline = content.find("\n", localstart) + 1
        with open(fpath, 'w') as f:
            f.write(content[0:nextline])
            f.write(target)
            f.write(content[nextline:])


    def hook(self, cls, methods):
        filepath = self.findfile(cls)
        if(len(filepath) == 1):
            print('\033[31m%s\033[0m'%(filepath))
            for method, parm, target in methods:
                content = readfromfile(filepath[0])
                poss = self.findMethod(content, method, parm)
                if (len(poss) == 1):
                    print('\033[31m%s\033[0m'%(method))
                    self.insertTarget(filepath[0], content, poss[0], target)
                else:
                    print('error method match %s size= %d'%(method, len(poss)))
        else:
            print("\033[31mfound [%s] path is not only one:%s\033[0m"%(cls, str(filepath)))



if __name__ == "__main__":
    processdir = os.path.dirname(os.path.realpath(__file__))
    sh = smaliHook(sys.argv[1])

    configcontent = readfromfile(processdir + '/config.py')
    configs:dict = eval(configcontent)
    for clasname,methods in configs.items():
        sh.hook(clasname, methods)

